package lumis.portal.presentation.core;

import java.io.Serializable;

import javax.servlet.http.HttpServletResponse;

import lumis.util.PortalUtil;

import org.apache.wicket.Component;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Session;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupException;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.basic.MultiLineLabel;
import org.apache.wicket.markup.html.debug.PageView;
import org.apache.wicket.protocol.http.WebRequestCycle;
import org.apache.wicket.protocol.http.WebResponse;


/**
 * Exception error page used by {@link LayoutFileApplication}.
 * @since 6.0.0
 */
public class LayoutFileExceptionErrorPage extends WebPage
{
	/**
	 * Adjusts the href attribute for the component.
	 * <p>
	 * Replacing putting in the beginning of the original value the contextPath.
	 * 
	 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
	 * @since 6.0.0
	 */
	@SuppressWarnings("serial")
	private static class HrefAdjustmentComponent extends WebComponent
	{
		public HrefAdjustmentComponent(String id)
		{
			super(id);
		}
		
		@Override
		protected void onComponentTag(ComponentTag tag)
		{
			// inserts the context path before the original url.
			String originalHref = tag.getAttribute("href");
			tag.put("href", ((WebRequestCycle)getRequestCycle()).getWebRequest().getHttpServletRequest().getContextPath() + "/" + originalHref);
			
			super.onComponentTag(tag);
		}
	}
	
	/**
	 * Contains information about the exception happened during a page rendering.
	 *
	 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
	 * @since 6.0.0
	 */
	@SuppressWarnings("serial")
	static class ExceptionInfo implements Serializable
	{
		private final RuntimeException exception;
		private final Page page;
		
		public ExceptionInfo(RuntimeException exception, Page page)
		{
			this.exception = exception;
			this.page = page;
		}
	}

	/**
	 * The meta data key where the {@link ExceptionInfo} is expected to be stored
	 * in the {@link RequestCycle}.
	 * @since 6.0.0
	 */
	@SuppressWarnings("serial")
	private static MetaDataKey<ExceptionInfo> EXCEPTION_INFO_KEY = new MetaDataKey<ExceptionInfo>() {};
	
	/**
	 * Sets the exception information in the session.
	 * @param session the session.
	 * @param exceptionInfo the exception information.
	 * @see #fetchExceptionInfo(Session)
	 * @since 6.0.0
	 */
	static void setExceptionInfo(Session session, ExceptionInfo exceptionInfo)
	{
		session.setMetaData(EXCEPTION_INFO_KEY, exceptionInfo);
	}
	
	/**
	 * Gets the exception information from the session, removing it from there.
	 * @param session the session.
	 * @return the exception information.
	 * @see #setExceptionInfo(Session, ExceptionInfo)
	 * @since 6.0.0
	 */
	private static ExceptionInfo fetchExceptionInfo(Session session)
	{
		ExceptionInfo result = session.getMetaData(EXCEPTION_INFO_KEY);
		session.setMetaData(EXCEPTION_INFO_KEY, null);
		return result;
	}
	
	public LayoutFileExceptionErrorPage()
	{
		ExceptionInfo exceptionInfo = fetchExceptionInfo(getSession());
		if (exceptionInfo == null)
			exceptionInfo = new ExceptionInfo(new IllegalStateException("No error information available."), null);
		
		final Throwable throwable = exceptionInfo.exception;
		final Page page = exceptionInfo.page;
		
		// Add exceptionDetails label
		String exceptionDetailsHtml;
		try
		{
			exceptionDetailsHtml = PortalUtil.getExceptionDetailsHtml(throwable, getWebRequestCycle().getWebRequest().getHttpServletRequest());
		}
		catch (Exception e)
		{
			exceptionDetailsHtml = "Could not obtain exception details: " + e.toString();
		}
		Label exceptionDetails = new Label("exceptionDetails", exceptionDetailsHtml);
		exceptionDetails.setEscapeModelStrings(false);
		add(exceptionDetails);

		// Get markup stream
		MarkupStream markupStream;
		if (throwable instanceof MarkupException)
			markupStream = ((MarkupException)throwable).getMarkupStream();
		else if (throwable instanceof LayoutFileMarkupException)
			markupStream = ((LayoutFileMarkupException)throwable).getMarkupStream();
		else
			markupStream = null;
			
		// get markup information
		String markup;
		String resource;
		if (markupStream != null)
		{
			markup = markupStream.toHtmlDebugString();
			resource = markupStream.getResource().toString();
		}
		else
		{
			markup = "";
			resource = "";
		}
		
		// adjust link of shortcut icon
		HrefAdjustmentComponent shortcutIcon = new HrefAdjustmentComponent("shortcuticon");
		add(shortcutIcon);
		
		// adjust link of stylesheet
		HrefAdjustmentComponent stylesheet = new HrefAdjustmentComponent("stylesheet");
		add(stylesheet);
		
		// Create markup label
		final MultiLineLabel markupLabel = new MultiLineLabel("markup", markup);
		markupLabel.setEscapeModelStrings(false);

		// Add container with markup highlighted
		final WebMarkupContainer markupHighlight = new WebMarkupContainer("markupHighlight");
		markupHighlight.add(markupLabel);
		markupHighlight.add(new Label("resource", resource));
		add(markupHighlight);

		// Show container if markup stream is available
		markupHighlight.setVisible(markupStream != null);

		// add component tree information
		PageView pageView = new PageView("componentTree", page);
		add(pageView);
		// verifies if the page has any child component
		boolean hasChildren = page == null ? false : page.visitChildren(new IVisitor<Component>()
		{
			public Object component(Component component)
			{
				return true;
			}
		}) != null;
		// componentTree is to be visible only if it has children to show
		pageView.setVisible(hasChildren);
	}

	@Override
	protected void configureResponse()
	{
		super.configureResponse();

		if (getWebRequestCycle().getResponse() instanceof WebResponse)
		{
			getWebRequestCycle().getWebResponse().getHttpServletResponse().setStatus(
				HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		}
	}

	@Override
	public boolean isErrorPage()
	{
		return true;
	}

	@Override
	public boolean isVersioned()
	{
		return false;
	}
}
